/*
 * Copyright (c) 2011 Imre Fazekas.
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Brillien nor the names of its
 * terms and concepts may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.vii.streamline.services;

import com.vii.streamline.services.error.StreamLineException;
import com.vii.streamline.services.json.JsonServices;
import com.vii.streamline.services.reflection.ReflectionServices;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

/**
 * Type related services to help type conversion and handling.
 */
public class TypeServices {

    public static Class getClass( String name ) throws ClassNotFoundException {
        return getClass( name, TypeServices.class.getClassLoader() );    
    }
    
    public static Class getClass( String name, ClassLoader l ) throws ClassNotFoundException {
        if( name.equals("float") )
            return float.class;
        if( name.equals("double") )
            return double.class;
        if( name.equals("byte") )
            return byte.class;
        if( name.equals("short") )
            return short.class;
        if( name.equals("int") )
            return int.class;
        if( name.equals("long") )
            return long.class;
        if( name.equals("boolean") )
            return boolean.class;

        return l.loadClass( name );
    }

    private static boolean convertToBoolean( Boolean b ){
        return b.booleanValue();
    }

    private static byte convertToByte( String s ){
        return Byte.parseByte( s );
    }
    private static byte convertToByte( Long l ){
        return (byte)l.longValue();
    }
    private static byte convertToByte( Integer l ){
        return (byte)l.intValue();
    }
    private static byte convertToByte( Short l ){
        return (byte)l.shortValue();
    }
    private static byte convertToByte( Float l ){
        return (byte)l.floatValue();
    }
    private static byte convertToByte( Double l ){
        return (byte)l.doubleValue();
    }
    private static byte convertToByte( Byte l ){
        return l;
    }
    private static byte convertToByte( BigDecimal decimal ){
        return decimal.byteValue();
    }


    private static int convertToInt( String s ){
        return Integer.parseInt( s );
    }
    private static int convertToInt( Long l ){
        return (int)l.longValue();
    }
    private static int convertToInt( Integer l ){
        return l;
    }
    private static int convertToInt( Short l ){
        return l;
    }
    private static int convertToInt( Float l ){
        return (int)l.floatValue();
    }
    private static int convertToInt( Double l ){
        return (int)l.doubleValue();
    }
    private static int convertToInt( Byte l ){
        return l;
    }
    private static int convertToInt( BigDecimal decimal ){
        return decimal.intValue();
    }

    private static int convertToInteger( String s ){
        return Integer.parseInt( s );
    }
    private static int convertToInteger( Long l ){
        return (int)l.longValue();
    }
    private static int convertToInteger( Integer l ){
        return l;
    }
    private static int convertToInteger( Short l ){
        return l;
    }
    private static int convertToInteger( Float l ){
        return (int)l.floatValue();
    }
    private static int convertToInteger( Double l ){
        return (int)l.doubleValue();
    }
    private static int convertToInteger( Byte l ){
        return l;
    }
    private static int convertToInteger( BigDecimal decimal ){
        return decimal.intValue();
    }

    private static long convertToLong( String s ){
        return Long.parseLong( s );
    }
    private static long convertToLong( Long l ){
        return l;
    }
    private static long convertToLong( Integer l ){
        return l;
    }
    private static long convertToLong( Short l ){
        return l;
    }
    private static long convertToLong( Float l ){
        return (long)l.floatValue();
    }
    private static long convertToLong( Double l ){
        return (long)l.doubleValue();
    }
    private static long convertToLong( Byte l ){
        return l;
    }
    private static long convertToLong( BigDecimal decimal ){
        return decimal.longValue();
    }

    private static short convertToShort( String s ){
        return Short.parseShort( s );
    }
    private static short convertToShort( Long l ){
        return (short)l.longValue();
    }
    private static short convertToShort( Integer l ){
        return (short)l.intValue();
    }
    private static short convertToShort( Short l ){
        return l;
    }
    private static short convertToShort( Float l ){
        return (short)l.floatValue();
    }
    private static short convertToShort( Double l ){
        return (short)l.doubleValue();
    }
    private static short convertToShort( Byte l ){
        return l;
    }
    private static short convertToShort( BigDecimal decimal ){
        return decimal.shortValue();
    }

    private static float convertToFloat( String s ){
        return Float.parseFloat( s );
    }
    private static float convertToFloat( Long l ){
        return l;
    }
    private static float convertToFloat( Integer l ){
        return l;
    }
    private static float convertToFloat( Short l ){
        return l;
    }
    private static float convertToFloat( Float l ){
        return l;
    }
    private static float convertToFloat( Double l ){
        return (float)l.doubleValue();
    }
    private static float convertToFloat( Byte l ){
        return l;
    }
    private static float convertToFloat( BigDecimal decimal ){
        return decimal.floatValue();
    }

    private static double convertToDouble( String s ){
        return Double.parseDouble( s );
    }
    private static double convertToDouble( Long l ){
        return l;
    }
    private static double convertToDouble( Integer l ){
        return l;
    }
    private static double convertToDouble( Short l ){
        return l;
    }
    private static double convertToDouble( Float l ){
        return l;
    }
    private static double convertToDouble( Double l ){
        return l;
    }
    private static double convertToDouble( Byte l ){
        return l;
    }    
    private static double convertToDouble( BigDecimal decimal ){
        return decimal.doubleValue();
    }

    private static String convertToString( String s ){
        return s;
    }
    private static String convertToString( Long l ){
        return l.toString();
    }
    private static String convertToString( Integer l ){
        return l.toString();
    }
    private static String convertToString( Short l ){
        return l.toString();
    }
    private static String convertToString( Float l ){
        return l.toString();
    }
    private static String convertToString( Double l ){
        return l.toString();
    }
    private static String convertToString( Byte l ){
        return l.toString();
    }
    private static String convertToString( BigDecimal decimal ){
        return decimal.toString();
    }

    /**
     * This method converts the given object to the given type. Conversion only between simple type is supported.
     * @throws StreamLineException in case of incapability of conversion
     */
    public static Object convert( Object o, Class type ) throws StreamLineException {
        if( o == null )
            return null;

        if( type.equals( String.class ) )
            return o.toString();

        if( type.isAssignableFrom( o.getClass() ) )
            return o;

        try {
            if(
                o instanceof HashMap &&
                !Map.class.isAssignableFrom( type )
            ){
                return TypeServices.fillObject( (HashMap)o, type );
            }

            Method m = ReflectionServices.getMethod(
                    TypeServices.class,
                    "convertTo" + StringServices.capitalize(type.getSimpleName()),
                    o.getClass()
            );
            return m.invoke( TypeServices.class, o );
        } catch (Exception e) {
            throw new StreamLineException( "Cannot convert " + o.getClass() + " to " + type, e );
        }
    }

    /**
     * This method tries to convert the given object to the given primitive type. Otherwise does nothing.
     /
    public static Object parse( Object o, Class type ) throws StreamLineException {
        if( o == null || !(o instanceof String) || !isSimpleType( type ) )
            return o;

        try {
            Method m = ReflectionServices.getMethod(
                    TypeServices.class,
                    "convertTo" + StringServices.capitalize( type.getSimpleName() ),
                    o.getClass()
            );
            return m.invoke( TypeServices.class, o );
        } catch (Exception e) {
            throw new StreamLineException( "Cannot convert " + o.getClass() + " to " + type, e );
        }
    }
    */
    
    public static boolean isVarargs( Class[] types ){
        return types[ types.length -1 ].isArray();
    }

    public static Object parse( String charSequence, Class type ) throws StreamLineException {
        return charSequence == null || charSequence.equalsIgnoreCase("null")
            ? null
            : ( isSimpleType( type )
                ? convert( JsonServices.parseJSON( charSequence ), type )
                : JsonServices.parseJSON( charSequence, type )
            );
    }

    public static Object parse( String charSequence, Class type, Map<String, Class> typeHints ) throws StreamLineException {
        return charSequence == null || charSequence.equalsIgnoreCase("null")
            ? null
            : ( isSimpleType( type )
                ? convert( JsonServices.parseJSON( charSequence ), type )
                : JsonServices.parseJSON( charSequence, type, typeHints )
            );
    }

    public static Object parse( String charSequence, Class type, String typeHint, Class hintClass ) throws StreamLineException {
        return charSequence == null || charSequence.equalsIgnoreCase("null")
            ? null
            : ( isSimpleType( type )
                ? convert( JsonServices.parseJSON( charSequence ), type )
                : JsonServices.parseJSON( charSequence, type, typeHint, hintClass )
            );
    }

    /**
     * This method tries to parse the charSequences and convert into the given types.
     * All simple types can be simple parsed, in other cases, the strings will be parsed as json messages.
     * @throws StreamLineException
     */
    public static Object[] parse( String[] charSequences, Class[] types ) throws StreamLineException {
        if( charSequences.length == 0 && types.length == 0 )
            return new Object[]{ };
        try{
            Object[] os;
            if( !isVarargs(types) ){
                os = new Object[ charSequences.length ];
                for( int i=0; i<os.length; ++i ){
                    os[i] = parse( charSequences[i], types[i] );
                }
            }
            else{
                os = new Object[ types.length ];
                for( int i=0; i<types.length-1; ++i ){
                    os[i] = parse( charSequences[i], types[i] );
                }
                Object[] array = (Object[])Array.newInstance( types[types.length-1].getComponentType(), charSequences.length-types.length+1 );
                for( int i=0; i<array.length; ++i ){
                    array[i] = JsonServices.parseJSON( charSequences[ types.length-1 + i ] );
                }
                os[types.length-1] = array;
            }
            return os;
        } catch(Exception e){
            throw new StreamLineException( e.getMessage(), e);
        }
    }

    /**
     * Tells whether the given type means simple type including primitive types, wrapper types and string
     */
    public static boolean isSimpleType( Class c ){
        return  c.isPrimitive() ||
                Boolean.class.isAssignableFrom( c ) ||
                String.class.isAssignableFrom( c ) ||
                Number.class.isAssignableFrom( c );
    }

    /**
     * Map a Hashmap content into an object instance by pairing the keys with the fieldNames
     */
    public static <T> T fillObject(HashMap h, String typeName, ClassLoader... loaders ) throws StreamLineException {
        for( ClassLoader loader : loaders )
            try{
                return (T)TypeServices.fillObject( h, loader.loadClass( typeName ) );
            } catch (java.lang.ClassNotFoundException e) {
            } catch (Exception e) {
                throw new StreamLineException( e );
            }
        throw new StreamLineException( new java.lang.ClassNotFoundException( typeName ) );
    }

    public static <T> T fillObject(HashMap h, Class<T> type ) throws StreamLineException {
        T instance = null;

        try {
            instance = type.newInstance();
        } catch (InstantiationException e) {
            throw new StreamLineException( "Cannot instantiate the class: " + type );
        } catch (IllegalAccessException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        for( Object key : h.keySet() ){
            try{
                ReflectionServices.setFieldValueThroughSetMethod(
                    type, instance, key.toString(), h.get( key )
                );
            } catch( Exception e ){
                throw new StreamLineException( "Cannot set field:" + key, e );
            }
        }
        return instance;
    }

}
